#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <plat/gpio-cfg.h>

#define CHAR_DEVICE_NAME "chardev"
#define CHAR_DEVICE_MINOR_NUM 2
#define DEFAULT_CHAR_DEVICE_MAJOR 0
#define DEFAULT_CHAR_DEVICE_MINOR 0
#define CACHE_SIZE 1024
#define LED_NUM 2

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Bruce.zc_wang");

static int module_param_char_dev_major = DEFAULT_CHAR_DEVICE_MAJOR;
static int module_param_char_dev_minor = DEFAULT_CHAR_DEVICE_MINOR;

module_param(module_param_char_dev_major, int, S_IRUSR);
module_param(module_param_char_dev_minor, int, S_IRUSR);

static int ret = 0;
static dev_t char_dev_num;
static struct class *myclass;

struct my_char_dev {
	void *cache;
	unsigned long cache_size;
	struct cdev cdev;
};

static struct my_char_dev *char_dev;

static int led_gpios[] = {
	EXYNOS4_GPL2(0),
	EXYNOS4_GPK1(1),
};



/*打开操作*/
static int chardevnode_open(struct inode *inode, struct file *file){
	printk(KERN_EMERG "chardevnode_open is success!\n");

	return 0;
}
/*关闭操作*/
static int chardevnode_release(struct inode *inode, struct file *file){
	printk(KERN_EMERG "chardevnode_release is success!\n");

	return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
	switch(cmd)
	{
		case 0:
		case 1:
			if (arg > LED_NUM) {
				return -EINVAL;
			}

			gpio_set_value(led_gpios[arg], cmd);
			break;

		default:
			return -EINVAL;
	}
	
	printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %ld \n",cmd,arg);
	
	return 0;
}

ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
	return 0;
}

ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
	return 0;
}

loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
	return 0;
}
struct file_operations my_fops = {
	.owner = THIS_MODULE,
	.open = chardevnode_open,
	.release = chardevnode_release,
	.unlocked_ioctl = chardevnode_ioctl,
	.read = chardevnode_read,
	.write = chardevnode_write,
	.llseek = chardevnode_llseek,
};

static void init_char_devs(struct cdev *cdev, unsigned int index)
{
	cdev_init(cdev, &my_fops);
	cdev->owner = THIS_MODULE;
	cdev_add(cdev, MKDEV(MAJOR(char_dev_num), MINOR(char_dev_num) + index),1);
}

static int gpio_init(void){
	int i=0,ret;
	
	for(i=0;i<LED_NUM;i++){
		ret = gpio_request(led_gpios[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed, ret = %d\n", CHAR_DEVICE_NAME,i,ret);
			return -1;
		}
		else{
			s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
			gpio_set_value(led_gpios[i], 1);			
		}
	}
	
	return 0;
}

static int chardev_init(void)
{
	int loop_i;

	printk(KERN_EMERG "char device major number: %d\n", module_param_char_dev_major);
	printk(KERN_EMERG "char device minor number: %d\n", module_param_char_dev_minor);

	if(module_param_char_dev_major != 0)
	{
		char_dev_num = MKDEV(module_param_char_dev_major, module_param_char_dev_minor);
		ret = register_chrdev_region(char_dev_num, CHAR_DEVICE_MINOR_NUM,CHAR_DEVICE_NAME);
		if(ret < 0)
		{
			printk(KERN_EMERG "register_chardev_region() failed\n");
		}
	}
	else
	{
		ret = alloc_chrdev_region(&char_dev_num, 6, CHAR_DEVICE_MINOR_NUM, CHAR_DEVICE_NAME);
		if(ret < 0)
		{
			printk(KERN_EMERG "alloc_chrdev_region() failed!!!\n");
		}
		else
		{
			printk(KERN_EMERG "alloc register char dev num sucessfully!!!\n\rmajor num = [%d], minor num = [%d]\n", MAJOR(char_dev_num), MINOR(char_dev_num));
		}
	}


	if(ret == 0)
	{
		char_dev = (struct my_char_dev *)kmalloc(sizeof(struct my_char_dev) * CHAR_DEVICE_MINOR_NUM, GFP_KERNEL);
		if(char_dev == NULL)
		{
			ret = -ENOMEM;
			printk(KERN_EMERG "kmalloc failed!!!\n");
			goto fail;
		}

		myclass = class_create(THIS_MODULE,CHAR_DEVICE_NAME);
		memset(char_dev, 0, sizeof(struct my_char_dev) * CHAR_DEVICE_MINOR_NUM);
		
		for(loop_i = 0; loop_i < CHAR_DEVICE_MINOR_NUM; loop_i++)
		{
			char_dev[loop_i].cache = kmalloc(CACHE_SIZE,GFP_KERNEL);
			memset(char_dev[loop_i].cache, 0, sizeof(CACHE_SIZE));
			init_char_devs(&char_dev[loop_i].cdev, loop_i);
			device_create(myclass,NULL,MKDEV(MAJOR(char_dev_num),MINOR(char_dev_num)+loop_i),NULL,CHAR_DEVICE_NAME"%d",loop_i);
		}
	}

	gpio_init();

	return 0;

fail:
	unregister_chrdev_region(char_dev_num, CHAR_DEVICE_MINOR_NUM);
	return ret;
}

static void chardev_exit(void)
{
	int loop_i;

	if(ret == 0)
	{
		printk(KERN_EMERG "unregister char device[%s]\n", CHAR_DEVICE_NAME);
		for(loop_i = 0;loop_i < CHAR_DEVICE_MINOR_NUM;loop_i++)
		{
			if(char_dev[loop_i].cache != NULL)
			{
				kfree(char_dev[loop_i].cache);
				char_dev[loop_i].cache = NULL;
			}
			cdev_del(&char_dev[loop_i].cdev);
			device_destroy(myclass,MKDEV(MAJOR(char_dev_num),MINOR(char_dev_num)+loop_i));
		}

		class_destroy(myclass);
		if(char_dev != NULL)
		{
			kfree(char_dev);
			char_dev = NULL;
		}
		unregister_chrdev_region(char_dev_num, CHAR_DEVICE_MINOR_NUM);
	}
	else
	{
		printk(KERN_EMERG "char device[%s] not register, unregister failed!!!\n", CHAR_DEVICE_NAME);
	}
}

module_init(chardev_init);
module_exit(chardev_exit);



